package core;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
//import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.ArrayList;
import java.util.HashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import jxl.*;
import jxl.write.*;

public class Graph {
	
	UUID id;
	String name;
	public ArrayList<Entity> entities = new ArrayList<Entity>();
	public HashMap<Process,Integer> processes = new HashMap<Process,Integer>();
	
	ArrayList<Cell> cluster;
	int rela[][];
	int workload[];
	
	public Graph(String name){
		id = UUID.randomUUID();
		this.name = name;
	}
	
	public void addEntity(Entity e){
		this.entities.add(e);
	}
	
	public void addProc(Process p){
		this.processes.put(p,0);
	}
	
	// Analyze the structural information of input graph, and prepare for assignment
	public boolean anls(){
		
		cluster = new ArrayList<Cell>();
		
		// 对Process进行分组
		for(Process p1: processes.keySet()){
			
			
			ArrayList<Cell> cellPool = new ArrayList<Cell>();
			// Determine if the cluster already include p1
			
			// 这些Cell是互斥的
			for(Cell cell: cluster){
				
				boolean flag = false;
				for(Process p2 : cell.procList){
					if(p1.hasSameEntityIn(p2)){
						flag = true;
					}
				}
				if(flag==true){
					cellPool.add(cell);
				}
			}
			
			Cell newCell = new Cell();
			newCell.procList.add(p1);
			
			if(cellPool.size()>0){
				// Merge all the cells in cellPool
				for(Cell cell: cellPool){
					newCell.procList.addAll(cell.procList);
					cluster.remove(cell);		
				}
				if(newCell.procList.size()>10) return false; // Too many Process in one cell.
			}
			cluster.add(newCell);			
		}
		
		//对Entity进行划分
		for(Entity e:entities){
			if(e.procOut.size()>0){
				//
				Process p = e.procOut.get(0);
				for(Cell cell:cluster){				
					if(cell.procList.contains(p)) cell.entityList.add(e);
				}
			} else {
				//
				Process p = e.procIn.get(0);
				for(Cell cell:cluster){
					if(cell.procList.contains(p)) cell.entityList.add(e);
				}
			}
		}
		
		//接下来分析每个Cell之间的关系
		rela = new int[cluster.size()][cluster.size()];
		workload = new int[cluster.size()];
		
		for(Cell cell1: cluster){
			for(Process p:cell1.procList){
				int index1 = cluster.indexOf(cell1);
				workload[index1] ++;
				for(Cell cell2: cluster){
					if(!cell2.equals(cell1)){
						for(Entity e: cell2.entityList){
							if(p.entityIn.contains(e)||p.entityOut.contains(e)){
								rela[index1][cluster.indexOf(cell2)] ++;
							}
						}
					}
				}
			}
		}
		
		return true;
	}
	
	public void printCluster(){
		for(Cell cell:cluster){
			System.out.print("Cluster:"+cluster.indexOf(cell)+"\t");
			for(Process p:cell.procList){
				System.out.print("[P:"+p.name+"]");
			}
			System.out.print("\r\n\t\t");
			ArrayList<Entity> entities = cell.entityList;
			for(Entity e:entities){
				System.out.print("[E:"+e.name+"]");
			}
			System.out.println();
			System.out.println("--------------------------------------------------------");
		}
	}
	
	public int getTotalWorkload(){
		int v=0;
		for(int i:workload) v+=i;
		return v;
	}
	
	public void printWorkload(){
		System.out.print("Cluster workload:\t");
		String out = "{";
		for(int i:workload){
			out +=i+",";
		}
		out = out.substring(0, out.length()-1);
		out +="}";
		System.out.print(out);
		System.out.println();
	}
	
	public void printRela(){
		System.out.print("Cluster relationship:\t\r\n");
		String out = "{";
		for(int[] row:rela){
			out +="{";
			for(int i:row){
				out +=i+",";
			}
			out = out.substring(0, out.length()-1);
			out +="},\r\n";
		}
		out = out.substring(0, out.length()-3);
		out +="}";
		System.out.print(out);
		System.out.println();
	}
	
	public void preAssign(){
		if(Node.NodeList.size()==0){
			System.out.println("NodeList is not ready.");return; 
		}
		
		// Cal each cell workloadRatio
		String s = "Cell workloadRatio: {";
		for(Cell cell: cluster){
			cell.workloadRatio = (float) workload[cluster.indexOf(cell)]/this.getTotalWorkload();
			s += cell.workloadRatio + ",";	
		}
		s = s.substring(0,s.length()-1); s+="}"; System.out.println(s);
		
		// Cal each node initial relative effective capacity
		int totalRating = 0;
		for(Node node: Node.NodeList){
			totalRating += node.rating;
		}
		s = "Node init rec: {";
		for(Node node: Node.NodeList){
			node.rec = (float) node.rating/totalRating;
			s += node.rec + ",";
		}
		s = s.substring(0,s.length()-1); s+="}"; System.out.println(s);
		
		
		// Initial 两个寄存器
		ArrayList<Cell> unassignedCells = new ArrayList<Cell>();
		unassignedCells.addAll(cluster);
		ArrayList<Cell> cellsInAssignment = new ArrayList<Cell>();
		
		// 找到workloadRatio最大的cell
		Cell thisCell = null;
		float r = 0;
		for(Cell cell: cluster){
			if(cell.workloadRatio>r){
				thisCell = cell;
				r = cell.workloadRatio;
			}
		}
		
		// 将thisCell载入, 并更新相应Node的rec
		thisCell.node = Node.getNode();
		thisCell.node.rec = thisCell.node.rec - thisCell.workloadRatio;
		
		// 更新寄存器
		unassignedCells.remove(thisCell);
		cellsInAssignment.add(thisCell);
		
		//迭代进行分配
		while(!unassignedCells.isEmpty()){
			
			while(!cellsInAssignment.isEmpty()){
				// 取出一个Cell
				thisCell = cellsInAssignment.get(0);
				cellsInAssignment.remove(0);
				
				// 找到和thisCell有关系的Cell，并进行分类
				ArrayList<Cell> cellPoolA = new ArrayList<Cell>(); //双通 (<->)
				ArrayList<Cell> cellPoolB = new ArrayList<Cell>(); //单出 (this->)
				ArrayList<Cell> cellPoolC = new ArrayList<Cell>(); //单进 (this<-)
				for(Cell cell: cluster){
					if(rela[cluster.indexOf(thisCell)][cluster.indexOf(cell)]>0){
						if(rela[cluster.indexOf(cell)][cluster.indexOf(thisCell)]>0){
							if(unassignedCells.contains(cell)) cellPoolA.add(cell);
						} else {
							if(unassignedCells.contains(cell)) cellPoolB.add(cell);
						}
					} else {
						if(rela[cluster.indexOf(cell)][cluster.indexOf(thisCell)]>0){
							if(unassignedCells.contains(cell)) cellPoolC.add(cell);
						}
					}
				}
				
				/**
				 *  a) 分别对这些 Cell 进行预指派
				 *  b) 并将这些 Cell 放入 cellsInAssignment
				 */
				for(Cell cell: cellPoolA){
					cell.node = thisCell.node.getNodeBi();
					cell.node.rec = cell.node.rec - cell.workloadRatio;
					unassignedCells.remove(cell);
					cellsInAssignment.add(cell);
				}
				for(Cell cell: cellPoolB){
					cell.node = thisCell.node.getNodeOut();
					cell.node.rec = cell.node.rec - cell.workloadRatio;
					
					unassignedCells.remove(cell);
					cellsInAssignment.add(cell);
				}
				
				for(Cell cell: cellPoolC){
					cell.node = thisCell.node.getNodeIn();
					cell.node.rec = cell.node.rec - cell.workloadRatio;
					unassignedCells.remove(cell);
					cellsInAssignment.add(cell);
				}
				
				
				/**
				 *  a) 将 thisCell 移出 cellsInAssignment 
				 *  b) 放入 assignedCells
				 */
				cellsInAssignment.remove(thisCell);
			}
		}
	}
	
	public void printPreAssignment(){
		System.out.println("Pre-Assignment:");
		for(Cell cell: cluster){
			System.out.println("Cell-"+cluster.indexOf(cell));
			for(Process p: cell.procList){
				System.out.println("\t"+p.name+"\t"+cell.node.ip);
			}
			for(Entity e: cell.entityList){
				System.out.println("\t"+e.name+"\t"+cell.node.ip);
			}
		}
	}
	
	public void assignAndImplAllEntities(){
		for(Cell cell: cluster){
			for(Entity e: cell.entityList){
				e.assign(cell.node);
				
				for(Process p: e.procIn){
					for(Cell c: cluster){
						if(c.procList.contains(p)) e.nodesImplTo.add(c.node);
					}
				}
				for(Process p: e.procOut){
					for(Cell c: cluster){
						if(c.procList.contains(p)) e.nodesImplTo.add(c.node);
					}
				}
				for(Node node: e.nodesImplTo){
					e.implTo(node);
				}
				
				e.putTokens();
			}
		}
	}
	
	public void implAllProc(){
		for(Cell cell: cluster){
			for(Process p: cell.procList){
				p.implTo(cell.node);
			}
		}
	}
	
	public void unloadAllProc(){
		for(Cell cell: cluster){
			for(Process p: cell.procList){
				p.unload();
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public void getResult(){
		for(Cell cell: cluster){
			for(Entity e: cell.entityList){
				JSONArray tokens = e.getResult();
				for (int i = 0; i < tokens.length(); i++){
					HashMap<String,String> token = new HashMap<String,String>();
					String key = null;
					try{
						JSONObject tokenJson = tokens.getJSONObject(i);
						for(java.util.Iterator<String> iter = tokenJson.keys(); iter.hasNext(); ){
							key = iter.next();
							token.put(key,tokenJson.getString(key));
						}
					} catch (JSONException ex){
						ex.printStackTrace();
					}
					e.addToken(token);
				}
			}
		}
	}
	
	public void unloadAllEntities(){
		for(Cell cell: cluster){
			for(Entity e: cell.entityList){
				e.unload();
			}
		}
	}
	
	public Entity getEntityByName(String name){
		for(Entity e: this.entities){
			if(e.name.equals(name)) return e;
		}
		return null;
	}
	
	public void printResultToFile() {

		File log = new File("output");
		if (!log.exists())
			log.mkdir();
		String fileName = name + new SimpleDateFormat("@[yyyy-MM-dd] HH-mm-ss")
				.format(new Date());
		try {
			FileOutputStream fos = new FileOutputStream("output/" + fileName
					+ ".xls");
			jxl.write.WritableWorkbook wb = Workbook.createWorkbook(fos);
			
			for(Entity e: entities){
				
				WritableSheet sheet = wb.createSheet(e.name, 0); 
				
				int i = 1;
				for(HashMap<String, String> token : e.tokenPool){
					
					int j = 0;
					for(String key:token.keySet()){
						
						if(i==1){
							Label label = new Label(j, 0, key);
							sheet.addCell(label);
						}
						Label label = new Label(j, i, token.get(key));
						sheet.addCell(label);
						j++;
					}
					i++;
				}
			}
			
			/* 
			PrintStream ps = new PrintStream(out);
			for(Entity e: entities){
				ps.println(e.name);
				for(HashMap<String, String> token : e.tokenPool){
					String tokenString = "\t";
					for(String key:token.keySet()){
						tokenString += "["+key+"->"+token.get(key)+"]";
					}
					ps.println(tokenString);
				}
			}
			
			ps.close();
			*/
			wb.write();
			wb.close();
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (WriteException e) {
			e.printStackTrace();
		}
	}
	
	public void printNameAndId(){
		System.out.println("Graph [" + name +"->"+id.toString()+"]");
		System.out.println("Processes:");
		for(Process p : this.processes.keySet()){
			System.out.println("\t["+p.name+"->"+p.id.toString()+"]");
		}
		System.out.println("Entities:");
		for(Entity e : this.entities){
			System.out.println("\t["+e.name+"->"+e.id.toString()+"]");
		}
	}
}

class Cell{
	Node node;
	ArrayList<Process> procList = new ArrayList<Process>();
	ArrayList<Entity> entityList = new ArrayList<Entity>();
	float workloadRatio = 0;
}

